Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-5170] Improve API and internals for presence data #189

Merged
merged 5 commits into from
Dec 11, 2024

Conversation

lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Dec 10, 2024

Public API improvements

  • You can now pass any JSON value as presence data (previously, the top-level value had to be an object, and you could not pass arrays or nested objects). This brings us in line with CHA-PR2a.
  • Do not expose the userCustomData property of the presence data object in the public API; it’s an implementation detail.
  • Conform to the ExpressibleBy*Literal protocols, making it easier to create a JSON value.

I have deliberately chosen to make the data-arg variants of the presence operation methods take a non-optional PresenceData. This is to minimise confusion between the absence of presence data and a presence data with JSON value null. This is how I wrote this API in 20e7f5f, but I didn’t restore it properly in 4ee16bd.

The JSONValue type introduced here is based on the example given here.

Internals improvements

  • Fix the presence object that gets passed to ably-cocoa when the user specifies no presence data; we were previously passing an empty string as the presence data in this case. (See below for where I got the “correct” behaviour from.)
  • Simplify the way in which we decode the presence data received from ably-cocoa (i.e. don’t do a round-trip of JSON serialization and deserialization); this comes at the cost of not getting a little bit for free from Swift’s serialization mechanism, but I think it’s worth it

The behaviour of how to map the chat presence data public API to the object exchanged with the core SDK is not currently fully specified. So, the behaviour that I’ve implemented here is based on the behaviour of the JS Chat SDK at 69ea478. I’ve created spec issue ably/specification#256 in order to specify this stuff properly, but I’m in a slight rush to get this public API fixed before we release our first beta, so I’ll address this later.

Resolves #178.

Summary by CodeRabbit

  • New Features

    • Introduced a new JSONValue type-safe enumeration for representing various JSON data types.
    • Added PresenceDataDTO structure for handling presence data in a JSON format.
  • Improvements

    • Simplified presence data handling by using a dictionary format instead of custom data structures.
    • Enhanced error handling and logging mechanisms for presence operations.
    • Updated methods for sending reactions to use a new JSON object format.
  • Tests

    • Added comprehensive unit tests for JSONValue and PresenceDataDTO to ensure functionality and robustness.
    • Updated existing tests to reflect changes in presence data handling and reaction parameters.

These are not like the `Message.asQueryItems` method, since, unlike that
one, their result is not intended to be used as the query params of an
HTTP request.
Makes it clearer that we don’t care about data in these calls. I should
have done this in 4ee16bd.
This inconsistency with the `data` property was a mistake in 20e7f5f.
There was one place where we throw and one where we fail silently (even
though the surrounding code throws for a bunch of other reasons of
seemingly similar severity). So throw consistently.
Public API improvements:

- You can now pass any JSON value as presence data (previously, the
  top-level value had to be an object, and you could not pass arrays or
  nested objects). This brings us in line with CHA-PR2a.

- Do not expose the `userCustomData` property of the presence data
  object in the public API; it’s an implementation detail.

- Conform to the `ExpressibleBy*Literal` protocols, making it easier to
  create a JSON value.

I have deliberately chosen to make the data-arg variants of the presence
operation methods take a non-optional PresenceData. This is to minimise
confusion between the absence of presence data and a presence data with
JSON value `null`. This is how I wrote this API in 20e7f5f, but I didn’t
restore it properly in 4ee16bd.

The JSONValue type introduced here is based on the example given in [1].

Internals improvements:

- Fix the presence object that gets passed to ably-cocoa when the user
  specifies no presence data; we were previously passing an empty string
  as the presence data in this case. (See below for where I got the
  “correct” behaviour from.)

- Simplify the way in which we decode the presence data received from
  ably-cocoa (i.e. don’t do a round-trip of JSON serialization and
  deserialization); this comes at the cost of not getting a little bit for
  free from Swift’s serialization mechanism, but I think it’s worth it

The behaviour of how to map the chat presence data public API to the
object exchanged with the core SDK is not currently fully specified. So,
the behaviour that I’ve implemented here is based on the behaviour of
the JS Chat SDK at 69ea478. I’ve created spec issue [2] in order to
specify this stuff properly, but I’m in a slight rush to get this public
API fixed before we release our first beta, so I’ll address this later.

Resolves #178.

[1] https://www.douggregor.net/posts/swift-for-cxx-practitioners-literals/
[2] ably/specification#256
Copy link

coderabbitai bot commented Dec 10, 2024

Walkthrough

The pull request introduces significant changes to the handling of presence data in the AblyChat application. Key modifications include the simplification of data structures used for entering presence in chat rooms, refactoring of mock presence methods for flexibility, and the introduction of a new type-safe enumeration for JSON values. Additionally, methods across various classes have been updated to enforce stricter data handling and improve error logging. Overall, these changes streamline the presence management system and enhance the clarity of data interactions.

Changes

File Path Change Summary
Example/AblyChatExample/ContentView.swift Simplified showPresence() function by replacing custom data structure with a dictionary for status. Updated event data handling to reflect new data structure.
Example/AblyChatExample/Mocks/MockClients.swift Refactored MockPresence methods to support optional parameters. Updated get methods to return nil for data field instead of PresenceData instance.
Sources/AblyChat/DefaultPresence.swift Refactored presence methods to use PresenceDataDTO. Enhanced error handling and logging in presence management methods.
Sources/AblyChat/DefaultRoomReactions.swift Added @MainActor attribute for concurrency. Updated send method to change data format for reactions.
Sources/AblyChat/JSONValue.swift Introduced JSONValue enum for type-safe JSON representation, including computed properties and initializers for various JSON types.
Sources/AblyChat/Presence.swift Removed PresenceCustomData enum. Updated method signatures to require non-optional PresenceData. Modified PresenceMember initializer for optional presence data.
Sources/AblyChat/PresenceDataDTO.swift Introduced PresenceDataDTO structure for handling presence data in JSON format, with error handling for decoding.
Sources/AblyChat/RoomReactions.swift Renamed asQueryItems() method to asJSONObject() in SendReactionParams extension.
Tests/AblyChatTests/DefaultRoomReactionsTests.swift Updated assertions in tests to reflect changes in data format for reactions.
Tests/AblyChatTests/IntegrationTests.swift Simplified presence method calls by removing data: nil parameters. Updated presence event data handling to use a dictionary format.
Tests/AblyChatTests/JSONValueTests.swift Introduced unit tests for JSONValue structure, covering conversion to and from Ably Cocoa presence data.
Tests/AblyChatTests/PresenceDataDTOTests.swift Added unit tests for PresenceDataDTO, validating initialization and JSON conversion logic.

Assessment against linked issues

Objective Addressed Explanation
Introduce convenience shortcuts to presence enter/update/leave (178, ECO-5170) The changes do not implement the proposed shortcuts.

Possibly related PRs

Suggested reviewers

  • umair-ably
  • maratal
  • AndyTWF

🐰 "In the chat room, presence is key,
With status updates, simple as can be!
A dictionary now, no more complex ways,
Just hop in and share, brighten your days!
With JSON values, our data's so bright,
Let's celebrate changes, all feels just right!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
Sources/AblyChat/Presence.swift (1)

74-74: Clarify the Meaning of nil vs. JSONValue.null in Presence Data

The comment clarifies that nil indicates the absence of presence data, whereas a JSONValue of case .null represents an explicit null value. This distinction is important for correctly interpreting presence data.

Consider enhancing the documentation to emphasize this distinction, possibly by providing examples, to aid developers in understanding the difference.

Tests/AblyChatTests/PresenceDataDTOTests.swift (1)

4-41: Consider Adding Additional Test Cases for Complex Data Structures

While the existing tests cover basic scenarios, consider adding test cases with more complex PresenceData, such as nested objects or arrays, to ensure that PresenceDataDTO handles all possible JSON structures correctly.

For example, test with:

  • Nested JSON objects
  • Arrays of values
  • Mixed data types

This will enhance test coverage and ensure robustness.

Example/AblyChatExample/Mocks/MockClients.swift (1)

346-354: Consider removing redundant default parameter

The private leave(dataForEvent:) method includes a default parameter, which is redundant since the overloaded methods already handle both cases.

-    func leave(dataForEvent: PresenceData? = nil) async throws {
+    private func leave(dataForEvent: PresenceData?) async throws {
Tests/AblyChatTests/IntegrationTests.swift (1)

208-241: LGTM: Thorough presence data testing with opportunity for refactoring

The tests comprehensively verify presence operations with data. However, there's repetition in the test pattern that could be extracted into a helper method.

Consider creating a helper method to reduce repetition:

private func verifyPresenceOperation(
    operation: (PresenceData) async throws -> Void,
    expectedAction: PresenceEventType
) async throws {
    let data: [String: String] = ["randomData": "randomValue"]
    try await operation(data)
    let event = try await rxPresenceSubscription.first { _ in true }
    #expect(event.action == expectedAction)
    #expect(event.data == data)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b436fe5 and 80e8585.

📒 Files selected for processing (12)
  • Example/AblyChatExample/ContentView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (3 hunks)
  • Sources/AblyChat/DefaultPresence.swift (10 hunks)
  • Sources/AblyChat/DefaultRoomReactions.swift (1 hunks)
  • Sources/AblyChat/JSONValue.swift (1 hunks)
  • Sources/AblyChat/Presence.swift (3 hunks)
  • Sources/AblyChat/PresenceDataDTO.swift (1 hunks)
  • Sources/AblyChat/RoomReactions.swift (1 hunks)
  • Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (3 hunks)
  • Tests/AblyChatTests/JSONValueTests.swift (1 hunks)
  • Tests/AblyChatTests/PresenceDataDTOTests.swift (1 hunks)
🔇 Additional comments (24)
Sources/AblyChat/RoomReactions.swift (1)

32-35: LGTM! Clear documentation improvement

The renamed method asJSONObject() better reflects its purpose, and the added documentation clearly explains its use case with ably-cocoa's publish operation.

Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1)

42-42: LGTM! Test updated to match API changes

The test assertion has been correctly updated to use the renamed asJSONObject() method, maintaining test coverage while reflecting the API improvements.

Sources/AblyChat/DefaultRoomReactions.swift (2)

27-27: LGTM! Consistent with API changes

The send method correctly uses the renamed asJSONObject() method, maintaining consistency with the API improvements.


Line range hint 3-5: Verify if @mainactor is the best solution

While @mainactor fixes the task isolation error, the TODO comment suggests this might be temporary. Consider investigating alternative solutions as part of issue #83.

Tests/AblyChatTests/JSONValueTests.swift (1)

1-145: Unit tests are comprehensive and validate all JSONValue cases

The test suite thoroughly covers the conversion between JSONValue and Ably Cocoa presence data, including end-to-end serialization and deserialization. This ensures reliability and correctness of the JSON handling.

Sources/AblyChat/Presence.swift (3)

3-3: Simplify PresenceData with Typealias

The PresenceData typealias has been updated to JSONValue, simplifying the representation of presence data throughout the codebase.


65-65: Adjust Initializer to Accept Optional PresenceData

The initializer for PresenceMember now accepts an optional PresenceData? for the data parameter. This allows for representing the absence of presence data with nil, distinguishing it from a JSONValue.null.


9-13: Update Method Signatures to Require Non-Optional PresenceData

The enter, update, and leave methods in the Presence protocol now require a non-optional PresenceData parameter. This enforces that presence data must be provided when these methods are called, enhancing data integrity.

Please verify that all calls to these methods in the codebase provide a non-optional PresenceData. Run the following script to identify any calls passing nil:

✅ Verification successful

Based on the search results, I can now generate the final response:

Non-Optional PresenceData Parameter Usage is Consistent

The verification shows that all calls to enter, update, and leave methods across the codebase consistently provide non-optional PresenceData:

  • Test files use dictionary literals: ["randomData": "randomValue"]
  • Example app uses: ["status": "📱 Online"]
  • Implementation in DefaultPresence.swift uses transformed data: JSONValue.object(dto.toJSONObjectValue).toAblyCocoaPresenceData

No instances of nil usage were found, confirming that the non-optional parameter requirement is being followed correctly throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find calls to `enter`, `update`, and `leave` methods passing `nil` for the `data` parameter.

rg '\.(enter|update|leave)\(data:\s*nil\)' -t swift

Length of output: 53


Script:

#!/bin/bash
# Let's search for all calls to these methods to understand their usage patterns
rg '\.(enter|update|leave)\((data:)?[^)]' -t swift -A 1

Length of output: 2278


Script:

#!/bin/bash
# Let's also check the protocol implementations to understand how these methods are used
ast-grep --pattern 'func $_(data: PresenceData) async throws'

Length of output: 63


Script:

#!/bin/bash
# Let's check if there are any overloads without the data parameter
ast-grep --pattern 'func $_(data: $_) async throws'

Length of output: 53

Sources/AblyChat/DefaultPresence.swift (5)

95-101: Refactor enter Method for Clarity

The enter method has been refactored to include two overloads:

  • enter(data: PresenceData) which accepts presence data.
  • enter() which calls enter(optionalData: nil) internally.

This separation enhances clarity and enforces explicit data handling.


Line range hint 104-116: Private Method enter(optionalData:) Handles Optional Data

The private method enter(optionalData:) consolidates the logic for entering presence with optional data. It constructs a PresenceDataDTO with userCustomData: data, ensuring consistent data handling.


129-136: Apply Consistent Refactoring to update Method

Similar to enter, the update method has been refactored with two overloads and a private update(optionalData:) method. This promotes consistency and clarity in presence data operations.


163-170: Implement Consistent Pattern for leave Method

The leave method now follows the same pattern as enter and update, with overloads and a private leave(optionalData:) method. This consistency improves maintainability.


233-246: Improve Error Handling in decodePresenceDataDTO Method

The decodePresenceDataDTO method now properly handles cases where ablyCocoaPresenceData is nil by throwing a descriptive error. This ensures that unexpected data formats are correctly managed.

Sources/AblyChat/PresenceDataDTO.swift (3)

1-34: Introduce PresenceDataDTO for Structured Presence Data

The new PresenceDataDTO struct provides a clear and structured way to handle presence data, encapsulating the userCustomData. It includes methods for initializing from a JSONValue and converting back to a JSON object, which enhances serialization and deserialization processes.


17-23: Ensure Robust Initialization from JSON Value

The initializer init(jsonValue:) checks that the provided jsonValue is an object and correctly extracts userCustomData. This validation prevents runtime errors due to incorrect data types.


25-33: Accurately Convert DTO to JSON Object Value

The toJSONObjectValue computed property efficiently constructs a JSON object from the PresenceDataDTO, including userCustomData only when it is not nil. This ensures that unnecessary keys are not included in the serialized data.

Tests/AblyChatTests/PresenceDataDTOTests.swift (3)

7-17: Validate Initialization from JSON Value with Comprehensive Test Cases

The initWithJSONValue test function covers multiple scenarios, ensuring that PresenceDataDTO initializes correctly from various JSON inputs, including cases with missing keys and null values.


19-23: Test Failure when Initializing with Non-Object JSON Value

The initWithJSONValue_failsIfNotObject test confirms that an error is thrown when attempting to initialize PresenceDataDTO with a non-object JSON value. This ensures robust error handling in the presence data deserialization process.


27-40: Ensure Correct Serialization in toJSONObject Tests

The toJSONObject test function verifies that PresenceDataDTO serializes to a JSON object accurately, covering scenarios where userCustomData is nil, a non-null value, or explicitly .null.

Example/AblyChatExample/Mocks/MockClients.swift (3)

280-280: LGTM: Simplified presence data handling

The change to return nil for presence data in mock implementations aligns with the PR objectives to streamline the API.

Also applies to: 292-292


304-312: LGTM: Well-structured presence enter methods

The implementation provides a clean API with:

  • Overloaded methods for flexibility
  • Private shared implementation for DRY principle
  • Thread-safety maintained within the actor

325-333: LGTM: Consistent update method implementation

The update methods follow the same pattern as enter methods, maintaining API consistency.

Tests/AblyChatTests/IntegrationTests.swift (1)

179-179: LGTM: Simplified presence method calls

The removal of explicit nil parameters makes the API more concise while maintaining the same functionality.

Also applies to: 191-191

Example/AblyChatExample/ContentView.swift (1)

205-205: LGTM: Simplified presence data handling in UI

The changes streamline presence data handling by:

  • Using direct dictionary format for status
  • Accessing data through type-safe accessors

Also applies to: 211-211

✅ Verification successful

Presence data handling is consistent across the codebase

The verification confirms that:

  • The presence data format using dictionary literals is consistently used across the codebase
  • The access pattern using objectValue?["status"]?.stringValue is correctly implemented for reading presence data
  • Test cases demonstrate similar patterns for presence data handling with enter and update operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify consistent presence data access pattern across the codebase
rg -A 2 'objectValue\?\["status"\]'

Length of output: 493


Script:

#!/bin/bash
# Search for presence enter/update patterns to verify consistency
rg -A 2 'presence\.(enter|update).*data:'

Length of output: 2453

Sources/AblyChat/JSONValue.swift Show resolved Hide resolved
Sources/AblyChat/JSONValue.swift Show resolved Hide resolved
Copy link
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Introduce convinience shortcuts to presence enter/update/leave
2 participants